#define _GNU_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <signal.h>
#include <netdb.h>

#define ERR(source) (perror(source),\
		     fprintf(stderr,"%s:%d\n",__FILE__,__LINE__),\
		     exit(EXIT_FAILURE))

#define HERR(source) (fprintf(stderr,"%s(%d) at %s:%d\n",source,h_errno,__FILE__,__LINE__),\
		     exit(EXIT_FAILURE))

#define MSGCOUNT 10

int sethandler( void (*f)(int), int sigNo)
{
  struct sigaction act;
  memset(&act, 0, sizeof(struct sigaction));
  act.sa_handler = f;
  if (-1==sigaction(sigNo, &act, NULL))
    return -1;
  return 0;
}

int make_socket(void)
{
  int sock;
  sock = socket(PF_INET,SOCK_STREAM,0);
  if(sock < 0) ERR("socket");
  return sock;
}

struct sockaddr_in make_address(char *address, uint16_t port)
{
  struct sockaddr_in addr;
  struct hostent *hostinfo;
  addr.sin_family = AF_INET;
  addr.sin_port = htons (port);
  hostinfo = gethostbyname(address);
  if(hostinfo == NULL)HERR("gethostbyname");
  addr.sin_addr = *(struct in_addr*) hostinfo->h_addr;
  return addr;
}

int connect_socket(char *name, uint16_t port)
{
  struct sockaddr_in addr;
  int socketfd;
  socketfd = make_socket();
  addr=make_address(name,port);
  if(connect(socketfd,(struct sockaddr*) &addr,sizeof(struct sockaddr_in)) < 0)
  {
    if(errno!=EINTR) ERR("connect");
    else 
    { 
      fd_set wfds ;
      int status;
      socklen_t size = sizeof(int);
      FD_ZERO(&wfds);
      FD_SET(socketfd, &wfds);
      if(TEMP_FAILURE_RETRY(select(socketfd+1,NULL,&wfds,NULL,NULL))<0)
        ERR("select");
      if(getsockopt(socketfd,SOL_SOCKET,SO_ERROR,&status,&size)<0)
        ERR("getsockopt");
      if(0!=status) ERR("connect");
    }
  }
  return socketfd;
}

ssize_t bulk_read(int fd, char *buf, size_t count)
{
  int c;
  size_t len=0;
  do
  {
    c=TEMP_FAILURE_RETRY(read(fd,buf,count));
    if(c<0) return c;
    if(0==c) return len;
    buf+=c;
    len+=c;
    count-=c;
  }
  while(count>0);
  return len;
}

ssize_t bulk_write(int fd, char *buf, size_t count)
{
  int c;
  size_t len=0;
  do
  {
    c=TEMP_FAILURE_RETRY(write(fd,buf,count));
    if(c<0) return c;
    buf+=c;
    len+=c;
    count-=c;
  }
  while(count>0);
  return len;
}

void read_write(int read_fd, int write_fd)
{
  int count;
  char buf[MSGCOUNT];

  do
  {
    count=TEMP_FAILURE_RETRY(read(read_fd,buf,MSGCOUNT));
    /* printf("przeczytałem %d znaków\n", count); */
    if(count<0)
    {
      printf("DISCONNECT on READ\n");
      if(TEMP_FAILURE_RETRY(close(read_fd))<0)
        ERR("close");
      if(TEMP_FAILURE_RETRY(close(write_fd))<0)
        ERR("close");
      exit(EXIT_FAILURE);
    }
    if(count>0)
    {
      if(bulk_write(write_fd,buf,count)<count)
      {
        printf("DISCONNECT on WRITE\n");
      if(TEMP_FAILURE_RETRY(close(read_fd))<0)
        ERR("close");
      if(TEMP_FAILURE_RETRY(close(write_fd))<0)
        ERR("close");
      exit(EXIT_FAILURE);
      }
    }
  }
  while(count==MSGCOUNT);
}

void usage(char * name)
{
  fprintf(stderr,"USAGE: %s domain port\n",name);
}

int main(int argc, char** argv)
{
  int fd;
  fd_set base_rfds, rfds;

  if(argc!=3)
  {
    usage(argv[0]);
    return EXIT_FAILURE;
  }
  if(sethandler(SIG_IGN,SIGPIPE)) ERR("Seting SIGPIPE:");
  fd=connect_socket(argv[1],atoi(argv[2]));

  FD_ZERO(&base_rfds);
  FD_SET(0, &base_rfds);
  FD_SET(fd, &base_rfds);

  for(;;)
  {
    rfds=base_rfds;
    if(TEMP_FAILURE_RETRY(select(fd+1,&rfds,NULL,NULL,NULL))<0)
      ERR("select");

    if(fd!=-1 && FD_ISSET(fd, &rfds))
    {
      read_write(fd,1);
    }
    else if(FD_ISSET(0, &rfds))
    {
      read_write(0,fd);
    }
  }

  if(TEMP_FAILURE_RETRY(close(fd))<0)ERR("close");
  return EXIT_SUCCESS;
}
